package aufgabe_2_test.try02;

import java.math.BigInteger;
import java.util.Date;
import java.util.Random;
import static aufgabe_2_test.try02.RhoMessage.MsgType.*;

public class PollardInThread implements Runnable {
	/**
	 * state if it should run or not
	 */
	boolean isRunning;
	/**
	 * parent Worker for this working-thread
	 */
	Worker w;
	/**
	 * local Random Number Generator
	 */
	public static Random rnd = new Random();
	/**
	 * the value which will be factorized by Pollad-Rho-Methode
	 */
	BigInteger n;
	/**
	 * the adjustment value for the Pollard-Rho-Methode
	 */
	BigInteger a;
	/**
	 * pointer to this thread
	 */
	public Thread tp;
	private static BigInteger two = new BigInteger("2");
	private static BigInteger three = new BigInteger("3");
	/**
	 * value where A should be changed
	 */
	private static BigInteger HK = new BigInteger("1000000");
	/**
	 * Rho-Cycle counter
	 */
	private BigInteger cnt;
	/**
	 * start time for time measurement
	 */
	private long startTime;
	
	public PollardInThread(Worker w, BigInteger n, BigInteger a) {
		this.isRunning = true;
		this.n = n;
		this.w = w;
		this.a = a;
		this.cnt = BigInteger.ZERO;
	}
	/**
	 * Set the runnable to running or not...
	 * @param b state of runnable
	 */
	public void setRunning(boolean b){
		isRunning = b;
	}
	
	/**
	 * Generate a random big integer smaller than n, using local RNG.
	 * @param n the value which will be factorized by Pollad-Rho-Methode
	 * @return the next RandomBigInteger, smaller than n
	 */
	public static BigInteger nextRandomBigInteger(BigInteger n) {
		return nextRandomBigInteger(n,rnd);
	}
	
	/**
	 * Generate a random big integer smaller than n.
	 * @param n the value which will be factorized by Pollad-Rho-Methode
	 * @param rand Random number generator
	 * @return the next RandomBigInteger, smaller than n
	 */
	public static BigInteger nextRandomBigInteger(BigInteger n, Random rand) {
	    BigInteger result = new BigInteger(n.bitLength(), rand);
	    while( result.compareTo(n) >= 0 ) { // repeat until result being smaller than n 
	        result = new BigInteger(n.bitLength(), rand);
	    }
	    return result;
	}
	/**
	 * Compute the factorization of the given Number with the Pollard-Rho-Methode
	 * @param n the value which will be factorized by Pollad-Rho-Methode
	 * @param a the added value, must not be 0 or -2 !!!
	 * @return the result of the computation or a canceled event
	 */
	public RhoMessage PollardMethod(BigInteger n, BigInteger a){
		BigInteger x = nextRandomBigInteger(n,rnd);
		BigInteger y = new BigInteger(x.toByteArray()); 
		BigInteger p = BigInteger.ONE;
		BigInteger d = BigInteger.ZERO;
		while(p.equals(BigInteger.ONE) && isRunning){ // run until it is not 1 anymore or it will be finished
			x = x.multiply(x).add(a).mod(n);//(x*x+ a) % n; 
			y = y.multiply(y).add(a).mod(n);//(y*y+ a) % n; 
			y = y.multiply(y).add(a).mod(n);//(y*y+ a) % n; 
			d = y.subtract(x).mod(n);		//(y  -  x) % n; 
			p = n.gcd(d);
			cnt = cnt.add(BigInteger.ONE); // added a rho cycle
			if(cnt.compareTo(HK)>=0){	// too much computations done -> restart with new A
				x = nextRandomBigInteger(n,rnd);
				y = new BigInteger(x.toByteArray()); 
				p = BigInteger.ONE;
				a = generateNewA(n,w.workerId);
			}
		}// finished, now send message
		return new RhoMessage( p, RhoResult , !p.equals(n));
	}

	/**
	 * Calculate the Square Root of n.
	 * @param n the value whose square root should be generated
	 * @return the square root of n
	 */
	private static BigInteger sqrt(BigInteger n) {
		BigInteger a = BigInteger.ONE;
		BigInteger b = new BigInteger(n.shiftRight(5).add(BigInteger.valueOf(8)).toByteArray());
		while (b.compareTo(a) >= 0) {
			BigInteger mid = new BigInteger((a.add(b).shiftRight(1)).toByteArray());
			if (mid.multiply(mid).compareTo(n) > 0)
				b = mid.subtract(BigInteger.ONE);
			else
				a = mid.add(BigInteger.ONE);
		}
		return a.subtract(BigInteger.ONE);
	}

	/**
	 * test a given number for being prime
	 * @param bigi the given big integer which should be tested for being prime
	 * @return true if the number is a prime, else false
	 */
	private static boolean isPrime(BigInteger bigi) {
		BigInteger inc = two;
		BigInteger div = new BigInteger(bigi.toByteArray());
		if(div.mod(inc).equals(BigInteger.ZERO)){ // mod 2 = 0 ? true = no prime
			return false;
		}
		//use sqrt
		BigInteger sqrt = sqrt(bigi);
		for (BigInteger i = three; sqrt.compareTo(i) >= 0; i=i.add(inc)) { // check all odd numbers until the sqrt of the given number
			if (bigi.mod(i).equals(BigInteger.ZERO)) { // if mod i = 0 -> no prime
				return false;
			}
		}
		return true;
	}
	
	@Override
	public void run() {
		startTime = new Date().getTime(); //get start time
		while(isRunning){
			RhoMessage r =  PollardMethod(n,a); // do the Pollard-Rho-Method
//			if(r.getN().intValue() != -1 ) System.out.println(r.getN()); 
			if(r.mt != RhoCanceled && isPrime(r.getN())){ // check for prime, don't enter if calculation has been aborted
				sendResultToWorker( new RhoMessage(r.getN(),RhoFoundPrime,true, (new Date().getTime())-startTime) ); // send result
				setRunning(false); // go to sleep
			}
			a = generateNewA(n,w.workerId); // generate new A 
		}
		w.incCount(cnt); // add Rho-Cycles to worker statistics
		w.updateTime((new Date().getTime()) - startTime); // update computation time on worker
	}
	//1000602106143806596478722974273666950903906112131794745457338659266842446985022076792112309173975243506969710503
	/**
	 * send result of computation to worker
	 * @param res result which will be send
	 */
	private void sendResultToWorker(RhoMessage res){
		w.calcComplete(res);
	}
	/**
	 * generate a new value for "a" for Pollard-Rho-Methode
	 * @param n the value which will be factorized by Pollad-Rho-Methode
	 * @param workerId if generated number is lower than workerid, workerid will be used as a
	 * @return generated BigInteger
	 */
	public static BigInteger generateNewA(BigInteger n, int workerId) {
		BigInteger id = new BigInteger((int) (n.bitCount()*0.5),PollardInThread.rnd);
		if(BigInteger.valueOf(workerId).compareTo(id)>0) id = BigInteger.valueOf(workerId);
		return id;
	}
	/**
	 * sets a reference to the thread
	 * @param t the thread-reference
	 */
	public void setThread(Thread t) {
		this.tp = t;
	}
}
